#pragma once

#include <iostream>
#include <cerrno>
#include <memory>
#include "Connection.hpp"
#include "TcpServer.hpp"
#include "BaseService.hpp"
#include "Protocol.hpp"

// ConnectionHandler donnot care about the protocol, it just grarantees the completeness of the message
class ConnectionHandler
{
public:
    static void recver(Connection::ptr conn)
    {
        char buffer[max_buffer_size];
        while(true)
        {
            int n = ::recv(conn->getFd(), buffer, max_buffer_size - 1, 0);
            if(n > 0)
            {
                buffer[n] = '\0';
                conn->addInBuffer(buffer);
            }
            else
            {
                if(errno == EAGAIN)  // no data to read, inbuffer is empty
                {
                    break;
                }
                else if(errno == EINTR)
                {
                    continue;
                }
                else
                {
                    // realy error
                    conn->getExcepterCallback()(conn);  // turn to exception callback
                    return;
                }
            }
            // when execute here, there is all readable data in inbuffer
            std::cout << "[Debug] resv from fd: " << conn->getFd() << ", data: " << conn->getInBuffer() << std::endl;
            // handle the message (serilize, deserialize, encode, decode, etc)
            _handlerRequest(conn);
        }
    }

    static void sender(Connection::ptr conn)
    {
        std::string& outbuffer = conn->getOutBuffer();
        while(true)
        {
            ssize_t n = ::send(conn->getFd(), outbuffer.c_str(), outbuffer.size(), 0);
            if(n > 0)
            {
                outbuffer.erase(0, n);
                if(outbuffer.empty()) { break; }  // send all data, outbuffer is empty
            }
            else
            {
                if(errno == EAGAIN) { break; }  // no data to send, but outbuffer might not be empty
                else if(errno == EINTR) { continue; }
                else
                {
                    // realy error
                    conn->getExcepterCallback()(conn);  // turn to exception callback
                }
            }
        }

        // when execute here, this round of sending is done, but maybe there is still data in outbuffer
        // case 1: [errno == EAGAIN] -> outbuffer is not empty, set concern of write event to epoll fd
        if(!conn->outBufferEmpty())
        {
            conn->getReactor()->enableReadWrite(conn->getFd(), true, true);
        }
        // case 2: [outbuffer is empty] -> delete concern of write event to epoll fd
        else
        {
            conn->getReactor()->enableReadWrite(conn->getFd(), true, false);
        }
    }

    static void excepter(Connection::ptr conn)
    {
        std::cout << "[Debug] exception occurred in fd: " << conn->getFd() << ", it will be closed" << std::endl;
        // 1. remove the connection from the map in server(reactor)
        conn->getReactor()->removeConnection(conn->getFd());
        // 2. close the connection corresponding to the fd
        conn->closeFd();
    }
private:
    static void _handlerRequest(Connection::ptr conn)
    {
        std::string& inbuffer = conn->getInBuffer();
        std::string message;  // for store the message to be decoded
        std::unique_ptr<BaseService> service(new CalculateNS::Calculate());  // responsible for service processing
        // decode the message
        while(Protocol::PacketHandler::Decode(inbuffer, &message))
        {
            // now the message is a complete message
            auto req = std::make_shared<Protocol::Cal_Request>();  // create a request object
            req->Deserialize(message);  // deserialize the message

            // do the service processing
            auto resp = service->Do(req);

            // serialize the response
            std::string resp_str;
            resp->Serialize(&resp_str);
            // encode the response
            resp_str = Protocol::PacketHandler::Encode(resp_str);

            // pull the response to the outbuffer
            conn->addOutBuffer(resp_str);
        }

        // when execute here, outbuffer is not empty, so we can send it
        if(!conn->outBufferEmpty())// check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                               // check if the connection is valid)                                              &&                                                                                                                                                                                                                    conn->getOutBuffer().empty())                                                                                    // check if the outbuffer is empty
        {
            conn->getSenderCallback()(conn);  // turn to sender callback
        }
    }
private:
    const static int max_buffer_size = 1024;
};

